/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package Domain;

import Adapter.DomainAdapter;
import DataAccess.HmmWriter;
import be.ac.ulg.montefiore.run.jahmm.Hmm;
import be.ac.ulg.montefiore.run.jahmm.ObservationDiscrete;
import be.ac.ulg.montefiore.run.jahmm.OpdfDiscrete;
import be.ac.ulg.montefiore.run.jahmm.toolbox.KullbackLeiblerDistanceCalculator;
import java.io.PrintStream;
import java.util.ArrayList;

/**
 *
 * @author Allen
 */
public class Cluster {

    ArrayList<Hmm<ObservationDiscrete<Output>>> c_hmmlist;
    Hmm<ObservationDiscrete<Output>> avg_hmm;

    /*Create a Cluster with centroid of given hmm*/
    public Cluster(Hmm<ObservationDiscrete<Output>> hmm) {
        c_hmmlist = new ArrayList<Hmm<ObservationDiscrete<Output>>>();
        this.avg_hmm = DomainAdapter.cloneHmm(hmm);
    }

    public void add(Hmm<ObservationDiscrete<Output>> tmp_hmm) {
        c_hmmlist.add(tmp_hmm);
    }

    public void printCluster(PrintStream ps){
        double T[][] = new double[3][3];
        double O[][] = new double[3][3];

        DomainAdapter.toMatrix(this.getAvgHmm(), T, O);
        ps.print("T=");
        HmmWriter.writeMatrix(ps, T);
        ps.print("O=");
        HmmWriter.writeMatrix(ps, O);
        ps.println("#avg");

        for (int i = 0; i < c_hmmlist.size(); i ++){
            DomainAdapter.toMatrix(c_hmmlist.get(i), T, O);
            ps.print("T=");
            HmmWriter.writeMatrix(ps, T);
            ps.print("O=");
            HmmWriter.writeMatrix(ps, O);
            ps.println("#"+i);
        }
    }

    public double avg_distance_to(Hmm<ObservationDiscrete<Output>> hmm) {
        double sum = 0;
        for (int i = 0; i < c_hmmlist.size(); i++) {
            sum += distanceBetween(hmm, c_hmmlist.get(i));
        }
        return sum / c_hmmlist.size();
    }

    public double distance(Hmm<ObservationDiscrete<Output>> tmp_hmm) {
        return distanceBetween(tmp_hmm, avg_hmm);
    }

    public static double distanceBetween(Hmm<ObservationDiscrete<Output>> hmm1,
            Hmm<ObservationDiscrete<Output>> hmm2) {
        KullbackLeiblerDistanceCalculator klc =
                new KullbackLeiblerDistanceCalculator();
        double distance = (klc.distance(hmm1, hmm2)
                + klc.distance(hmm2, hmm1)) / 2;
        return distance;
    }

    public Hmm<ObservationDiscrete<Output>> getAvgHmm() {

        double T[][] = new double[3][3];
        double O[][] = new double[3][3];

        for (int i = 0; i < 3; i++) {
            for (int j = 0; j < 3; j++) {
                T[i][j] = O[i][j] = 0;
            }
        }
        int size = c_hmmlist.size();
        Hmm<ObservationDiscrete<Output>> tmp_hmm = null;
        Output loc_c = Output.c;
        Output loc_e = Output.e;
        Output loc_r = Output.r;

        for (int k = 0; k < size; k++) {
            tmp_hmm = c_hmmlist.get(k);
            for (int i = 0; i < 3; i++) {
                for (int j = 0; j < 3; j++) {
                    T[i][j] += tmp_hmm.getAij(i, j);
                }
            }
            for (int i = 0; i < 3; i++) {
                O[i][0] += tmp_hmm.getOpdf(i).probability(new ObservationDiscrete<Output>(loc_c));
                O[i][1] += tmp_hmm.getOpdf(i).probability(new ObservationDiscrete<Output>(loc_e));
                O[i][2] += tmp_hmm.getOpdf(i).probability(new ObservationDiscrete<Output>(loc_r));
            }
        }

        for (int i = 0; i < 3; i++) {
            for (int j = 0; j < 3; j++) {
                T[i][j] = T[i][j] / size;
                O[i][j] = O[i][j] / size;
            }
        }

        //setting the avg_hmm value
        for (int i = 0; i < 3; i++) {
            avg_hmm.setOpdf(i, new OpdfDiscrete<Output>(Output.class, O[i]));
        }
        for (int i = 0; i < 3; i++) {
            for (int j = 0; j < 3; j++) {
                avg_hmm.setAij(i, j, T[i][j]);
            }
        }

        return this.avg_hmm;
    }
}